﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Audio;

namespace LumenLiquid.Audio
{
    /// <summary>
    /// Supplies all Audio related stuff
    /// </summary>
    public class Audio
    {
        public struct AudioData
        {
            public string Soundbank;
            public string CueName;
        }

        string _root = "";
        AudioEngine _audioEngine;

        readonly Dictionary<string, SoundBank> _soundbank = new Dictionary<string, SoundBank>();
        readonly Dictionary<string, WaveBank> _wavebank = new Dictionary<string, WaveBank>();
        readonly Dictionary<string, Cue> _cues = new Dictionary<string, Cue>();
        readonly Dictionary<string, AudioCategory> _category = new Dictionary<string, AudioCategory>();

        /// <summary>
        /// Initializes an engine for one XACT-Project
        /// </summary>
        /// <param name="projectpath">The path to the projectfile. Wave and Soundbank have to be in the same folder</param>
        /// <param name="categories">The Categories of the project, because we can't read them out</param>
        public void Init(string projectpath, List<string> categories)
        {
            _root = projectpath.Replace(System.IO.Path.GetFileName(projectpath), "");
            _audioEngine = new AudioEngine(projectpath);

            for (int i = 0; i < categories.Count(); i++)
            {
                try
                {
                    _category.Add(categories[i], _audioEngine.GetCategory(categories[i]));
                    //TODO: implement Volume adjusting with settings database
                    //_category[categories[i]].SetVolume( / 100);
                }
                catch { }
            }
        }

        /// <summary>
        /// Adds a soundbank into the list
        /// </summary>
        /// <param name="soundbankname">The Name of the soundbank</param>
        public void AddSoundBank(string soundbankname)
        {
            _soundbank.Add(soundbankname, new SoundBank(_audioEngine, _root + soundbankname + ".xsb"));
        }

        /// <summary>
        /// Adds a wavebank into the list
        /// </summary>
        /// <param name="wavebankname">The Name of the wavebank</param>
        public void AddWaveBank(string wavebankname)
        {
            _wavebank.Add(wavebankname, new WaveBank(_audioEngine, _root + wavebankname + ".xwb"));
        }

        /// <summary>
        /// Removes a soundbank out of the list (if possible)
        /// </summary>
        /// <param name="soundbankname">The name of the soundbank to remove</param>
        /// <returns>When false, you first have to stop all cues of this soundbank</returns>
        public bool RemoveSoundBank(string soundbankname)
        {
            if (_soundbank.ContainsKey(soundbankname))
            {
                if (_soundbank[soundbankname].IsInUse)
                    return false;
                
                _soundbank.Remove(soundbankname);
            }

            return true;
        }

        /// <summary>
        /// Removes a wavebank out of the list (if possible)
        /// </summary>
        /// <param name="wavebankname">The name of the wavebank to remove</param>
        /// <returns>When false, you first have to stop all cues which use this wavebank</returns>
        public bool RemoveWaveBank(string wavebankname)
        {
            if (_wavebank.ContainsKey(wavebankname))
            {
                if (_wavebank[wavebankname].IsInUse)
                    return false;
                
                _wavebank.Remove(wavebankname);
            }

            return true;
        }

        /// <summary>
        /// Plays a cue of a soundbank
        /// </summary>
        /// <param name="soundbankname">The name of the soundbank with the cue</param>
        /// <param name="cuename">The Name of the cue</param>
        public void PlayCue(string soundbankname, string cuename)
        {
            try
            {
                if (_cues.ContainsKey(cuename))
                    _cues.Remove(cuename);

                _cues.Add(cuename, _soundbank[soundbankname].GetCue(cuename));
                _cues[cuename].Play();
            }
            catch { }
        }

        /// <summary>
        /// Plays a cue of a soundbank in a 3D room
        /// </summary>
        /// <param name="soundbankname">The name of the soundbank with the cue</param>
        /// <param name="cuename">The Name of the cue</param>
        /// <param name="listener">The position datas of the listener</param>
        /// <param name="source">The position datas of the source</param>
        public void PlayCue(string soundbankname, string cuename, AudioListener listener, AudioEmitter source)
        {
            try
            {
                if (_cues.ContainsKey(cuename))
                    _cues.Remove(cuename);

                _cues.Add(cuename, _soundbank[soundbankname].GetCue(cuename));
                _cues[cuename].Apply3D(listener, source);
                _cues[cuename].Play();
            }
            catch { }
        }

        /// <summary>
        /// Stops a specific cue
        /// </summary>
        /// <param name="cuename">The name of the cue</param>
        public void Stop(string cuename)
        {
            if (_cues.ContainsKey(cuename))
            {
                _cues[cuename].Stop(AudioStopOptions.AsAuthored);
                _cues.Remove(cuename);
            }
        }

        /// <summary>
        /// Stops every cue which is currently playing
        /// </summary>
        public void StopAll()
        {
            foreach (var cue in _cues.Values)
            {
                cue.Stop(AudioStopOptions.AsAuthored);
            }

            _cues.Clear();
        }

        /// <summary>
        /// pauses a specific cue
        /// </summary>
        /// <param name="cuename">The name of the cue to pause</param>
        public void Pause(string cuename)
        {
            if (_cues.ContainsKey(cuename))
            {
                _cues[cuename].Pause();
            }
        }

        /// <summary>
        /// Pauses all playing cues
        /// </summary>
        public void PauseAll()
        {
            foreach (var cue in _cues.Values)
            {
                cue.Pause();
            }
        }

        /// <summary>
        /// Resumes a specific cue
        /// </summary>
        /// <param name="cuename">The name of the cue to resume</param>
        public void Resume(string cuename)
        {
            if (_cues.ContainsKey(cuename))
            {
                _cues[cuename].Resume();
            }
        }

        /// <summary>
        /// Resumes all cues which are paused
        /// </summary>
        public void ResumeAll()
        {
            foreach (var cue in _cues.Values.Where(cue => cue.IsPaused))
            {
                cue.Resume();
            }
        }

        /// <summary>
        /// Returns the current Volume of any category
        /// </summary>
        /// <param name="category">The name of the category you wish to have</param>
        /// <returns></returns>
        public float GetVolume(string category)
        {
            try
            {
                //TODO: implement Volume return for categories
                //return (float)(Convert.ToInt32(Global.Settings.HeadKey.get_GetKey(Global.settings_music).get_GetValue(category))) / 100;
            }
            catch { return 0.0f; }

            return 0;
        }

        /// <summary>
        /// Sets the Volume of any category
        /// </summary>
        /// <param name="category">The category to change</param>
        /// <param name="volume">The new volume. can be between 0.0 and 2.0</param>
        public void SetVolume(string category, float volume)
        {
            if (volume <= 2.0f && volume >= 0.0f && _category.ContainsKey(category))
            {
                //TODO: implement setting volume also in database
               _category[category].SetVolume(volume);
            }
        }
    }
}
